package files

import (
	"io/fs"
	"path/filepath"
	"strings"

	"gitee.com/xuender/oils/logs"
	"gitee.com/xuender/oils/sets"
	"gitee.com/xuender/oils/times"
	"gorm.io/gorm"
)

// Service 服务.
type Service struct {
	ctx     *Context
	db      *gorm.DB
	ver     int
	ignores []string
}

// NewService 新建服务.
func NewService(
	ctx *Context,
	db *gorm.DB,
) *Service {
	return &Service{
		db:  db,
		ctx: ctx,
	}
}

// Add 增加目录.
func (p *Service) Add(path string) {
	path = logs.PanicString(filepath.Abs(path))
	logs.Debug("增加索引:", path)

	set := sets.NewStrings(p.Dirs()...)
	if set.Has(path) {
		logs.Debug("已经包含")

		return
	}

	for s := range set {
		if strings.HasPrefix(path, s) {
			logs.Debug("包含父目录", s)

			return
		}
	}

	for s := range set {
		if strings.HasPrefix(s, path) {
			logs.Debug("覆盖", s)
			p.RemoveDir(s)
		}
	}

	p.db.Create(&Config{
		Type:  ByteDir,
		Value: path,
	})
}

// Ignore 忽略目录.
func (p *Service) Ignore(path string) {
	logs.Debug("增加忽略:", path)

	set := sets.NewStrings(p.Ignores()...)
	if set.Has(path) {
		return
	}

	p.db.Create(&Config{
		Type:  ByteIgnore,
		Value: path,
	})
}

// RemoveIgnore 删除忽略.
func (p *Service) RemoveIgnore(path string) {
	logs.Debug("删除忽略:", path)
	p.remove(path, ByteIgnore)
}

// RemoveDir 删除忽略.
func (p *Service) RemoveDir(path string) {
	logs.Debug("删除目录:", path)
	p.remove(path, ByteDir)
}

// RemoveFiles 删除目录文件.
func (p *Service) RemoveFiles(path string) {
	p.db.Where("path like ?", path+"%").Delete(File{})
}

func (p *Service) remove(path string, t int) {
	p.db.Where("type=? and value=?", t, path).Delete(Config{})
}

// Ignores 搜索目录.
func (p *Service) Ignores() []string {
	configs := []Config{}
	p.db.Order("id").Where("type=?", ByteIgnore).Find(&configs)

	dirs := make([]string, len(configs))
	for i, c := range configs {
		dirs[i] = c.Value
	}

	return dirs
}

// Dirs 搜索目录.
func (p *Service) Dirs() []string {
	configs := []Config{}
	p.db.Order("id").Where("type=?", ByteDir).Find(&configs)

	dirs := make([]string, len(configs))
	for i, c := range configs {
		dirs[i] = c.Value
	}

	return dirs
}

// Index 索引.
func (p *Service) Index() {
	c := times.ClockStart()
	defer func() {
		logs.Info("索引耗时:", times.Natural(times.ClockStop(c)))
	}()

	p.ignores = p.Ignores()
	logs.Debug("ignores", p.ignores)
	p.ver++

	for _, path := range p.Dirs() {
		_ = filepath.Walk(path, p.walk)
	}

	r := p.db.Where("ver!=?", p.ver).Delete(&File{})
	logs.Debug("删除:", r.RowsAffected)
}

func (p *Service) walk(path string, info fs.FileInfo, err error) error {
	for _, i := range p.ignores {
		if strings.Contains(path, i) {
			return nil
		}
	}

	f := &File{
		Path:    path,
		ModTime: info.ModTime(),
		Ver:     p.ver,
	}
	r := p.db.Model(f).Where("mod_time=?", f.ModTime).Update("ver", f.Ver)

	if r.RowsAffected > 0 {
		return nil
	}

	f.Base = filepath.Base(path)
	f.Ext = filepath.Ext(path)
	f.Size = info.Size()
	f.Dir = info.IsDir()

	logs.Debug(path)
	p.db.Save(f)

	return nil
}

// Search 搜索.
func (p *Service) Search(args []string) []*File {
	ret := []*File{}
	d := p.db.
		Limit(p.ctx.Limit).
		Offset(p.ctx.Offset)

	if p.ctx.Desc {
		d.Order(p.ctx.Order + " desc")
	} else {
		d.Order(p.ctx.Order)
	}

	p.where(args, d).Find(&ret)

	return ret
}

func (p *Service) where(args []string, tx *gorm.DB) *gorm.DB {
	for _, arg := range args {
		if p.ctx.Regexp {
			if !p.ctx.HasRegexp {
				panic("缺少regexp.so, 不能支持正则表达式")
			}

			tx.Where("base regexp ?", arg)

			continue
		}

		if strings.Contains(arg, "*") || strings.Contains(arg, "%") || strings.Contains(arg, "?") {
			arg = strings.ReplaceAll(arg, "*", "%")
			if strings.HasPrefix(arg, "^") {
				tx.Where("base not like ?", arg[1:])

				continue
			}

			tx.Where("base like ?", arg)

			continue
		}

		if strings.HasPrefix(arg, "^") {
			tx.Where("base not like ?", "%"+arg[1:]+"%")

			continue
		}

		tx.Where("base like ?", "%"+arg+"%")
	}

	if p.ctx.Ext != "" {
		tx.Where("ext=?", p.ctx.Ext)
	}

	return tx
}

// Count 统计.
func (p *Service) Count(args []string) int64 {
	if len(args) == 0 {
		return 0
	}

	ret := int64(0)
	d := p.db.
		Model(&File{})

	p.where(args, d).Count(&ret)

	return ret
}
